﻿/*
	wp8libflac project
	© Alovchin, 2014
*/

using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Media.Core;
using Windows.Media.MediaProperties;
using Windows.Storage.Streams;
using wp8.Interop;
using wp8.MediaHelpers;

namespace wp81cs.Streaming
{
    internal sealed class FlacMediaSourceAdapter : MediaSourceAdapter
    {
        private double _currentTime;
        private long _startPosition;

        private FlacWaveStream _flacWaveStream;
        private ConcurrentQueue<IBuffer> _buffersQueue;

        public FlacMediaSourceAdapter(string filePath)
        {
            this._buffersQueue = new ConcurrentQueue<IBuffer>();
            this._flacWaveStream = new FlacWaveStream(filePath);

            FlacStreamInfo streamInfo = this._flacWaveStream.GetStreamInfo();

            AudioEncodingProperties encodingProperties = AudioEncodingProperties.CreatePcm(
                streamInfo.SampleRate, streamInfo.ChannelCount, streamInfo.BitsPerSample);

            CreateMediaSource(encodingProperties);
        }

        protected override void OnMediaSourceStarting(
            MediaStreamSource sender, MediaStreamSourceStartingEventArgs e)
        {
            MediaStreamSourceStartingRequestDeferral deferral = e.Request.GetDeferral();

            FlacStreamInfo streamInfo = this._flacWaveStream.GetStreamInfo();
            sender.Duration = TimeSpan.FromSeconds(streamInfo.Duration);
            sender.CanSeek = true;

            this._startPosition = this._flacWaveStream.Position;
            this._currentTime = 0;

            double startTime = this._flacWaveStream.GetDurationFromBufferSize((uint) this._startPosition);
            e.Request.SetActualStartPosition(TimeSpan.FromSeconds(startTime));

            deferral.Complete();
        }

        protected override async void OnMediaSourceSampleRequested(
            MediaStreamSource sender, MediaStreamSourceSampleRequestedEventArgs e)
        {
            MediaStreamSourceSampleRequestDeferral deferral = e.Request.GetDeferral();

            var instantBuffer = this.GetBuffer();
            var buffer = await this._flacWaveStream.ReadAsync(instantBuffer, instantBuffer.Capacity, InputStreamOptions.None);

            MediaStreamSample sample;
            if (buffer.Length > 0)
            {
                sample = MediaStreamSample.CreateFromBuffer(
                    buffer, TimeSpan.FromSeconds(this._currentTime));
                sample.Processed += this.OnSampleProcessed;

                double sampleDuration = this._flacWaveStream.GetDurationFromBufferSize(buffer.Length);
                sample.Duration = TimeSpan.FromSeconds(sampleDuration);

                this._currentTime += sampleDuration;
            }
            else
            {
                sample = null;

                this._flacWaveStream.Seek(this._startPosition);
                this._currentTime = 0;
            }

            e.Request.Sample = sample;
            deferral.Complete();
        }

        private void OnSampleProcessed(MediaStreamSample sender, object args)
        {
            this._buffersQueue.Enqueue(sender.Buffer);
            sender.Processed -= this.OnSampleProcessed;
        }

        private IBuffer GetBuffer()
        {
            IBuffer buffer;
            bool dequeued = this._buffersQueue.TryDequeue(out buffer);
            if (!dequeued)
                buffer = WindowsRuntimeBuffer.Create(4096);
            return buffer;
        }

        protected override void OnMediaSourceClosed(
            MediaStreamSource sender, MediaStreamSourceClosedEventArgs e)
        {
            this._currentTime = 0;
            this._flacWaveStream.Close();
            RaiseStreamComplete();
        }

        protected override void Dispose(bool isDisposing)
        {
            if (isDisposing)
            {
                if (this._flacWaveStream != null)
                {
                    this._flacWaveStream.Dispose();
                    this._flacWaveStream = null;
                }

                this._buffersQueue = null;
            }
        }
    }
}